import { defineStore } from "pinia";
import {
  getAccountAggregation,
  refreshAccountAggregation
} from "@/api/account";
import {
  type AccountAggregationSchema,
  type AccountInfoSchema,
  type AccountSchema,
  type PositionSchema,
  type OrderSchema,
  type TradeSchema,
  convertToMapStructure,
  type LogSchema,
  type RiskReportSchema
} from "@/types/account.types";
import { EventType, type ServerSentEvent } from "@/types/sse.types";
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { eventEngine } from "@/services/eventEngine";

export const useAccountAggregationStore = defineStore(
  "accountAggregation",
  () => {
    // Constants
    const MAX_CACHE_SIZE = 1000;
    const PUBLISH_INTERVAL_MS = 200;

    // Cache
    let cachedEvents = new Array<ServerSentEvent>();

    // State
    const isLoading = ref(false);
    const isInitialized = ref(false);
    const lastUpdateTime = ref<string | null>(null);

    // Data state
    // gateway_name -> AccountInfoSchema
    const aggregation = ref<Map<string, AccountInfoSchema>>(new Map());
    const currentEventId = ref("");

    /** publish 时计算各汇总值，避免每个tick都触发计算
     */
    let publishTimer: ReturnType<typeof setTimeout> | null = null;

    function calcSummaryFields(): void {
      const toNumeric = (value: unknown): number =>
        typeof value === "number" && Number.isFinite(value) ? value : 0;

      aggregation.value.forEach(accountInfo => {
        let totalPnl = 0;
        let totalOptionMarketValue = 0;
        let totalOptionTimeValue = 0;
        let totalIntrinsicValue = 0;

        accountInfo.positions.forEach(position => {
          totalPnl += toNumeric(position.pnl);
          totalOptionMarketValue += toNumeric(position.option_market_value);
          totalOptionTimeValue += toNumeric(position.option_time_value);
          totalIntrinsicValue += toNumeric(position.option_intrinsic_value);
        });

        accountInfo.totalPnl = totalPnl;
        accountInfo.totalOptionMarketValue = totalOptionMarketValue;
        accountInfo.totalOptionTimeValue = totalOptionTimeValue;
        accountInfo.totalMarketValue =
          totalIntrinsicValue + totalOptionTimeValue;
      });
    }

    function publish(): void {
      calcSummaryFields();
    }

    function schedulePublish(): void {
      if (publishTimer !== null) return;
      publishTimer = setTimeout(() => {
        publishTimer = null;
        publish();
      }, PUBLISH_INTERVAL_MS);
    }

    function forcePublish(): void {
      if (publishTimer !== null) {
        clearTimeout(publishTimer);
        publishTimer = null;
      }
      publish();
    }

    // Get connection status from the EventEngine
    const { isConnected } = eventEngine.getConnectionStatus();

    // Actions
    /**
     * Initialize the account aggregation data and register with the EventEngine
     */
    async function init() {
      // Only initialize if not already initialized or loading
      if (isLoading.value || isInitialized.value) {
        console.log(
          "Account aggregation store is already loading/initialized, skip init"
        );
        return;
      }

      try {
        // Register events
        eventEngine.on(EventType.ACCOUNT, handleEvent);
        eventEngine.on(EventType.POSITION, handleEvent);
        eventEngine.on(EventType.ORDER, handleEvent);
        eventEngine.on(EventType.TRADE, handleEvent);
        eventEngine.on(EventType.LOG, handleEvent);
        eventEngine.on(EventType.RISK_REPORT, handleEvent);
        console.log("Account aggregation store registered event handlers");

        // Fetch initial data
        await fetchAggregation();

        console.log("Account aggregation store initialized successfully");
      } catch (err) {
        console.error(`账户聚合数据初始化失败：${err}`);
        ElMessage.error(`账户聚合数据初始化失败，准备重试！`);

        // Schedule retry
        setTimeout(init, 5000);

        // Re-throw the error to be handled by the caller
        throw err;
      }
    }

    /**
     * Handle events from the EventEngine
     */
    function handleEvent(event: ServerSentEvent) {
      // If not initialized or loading is in progress, cache the event
      if (!isInitialized.value || isLoading.value) {
        const logMessage = !isInitialized.value
          ? "Caching event until initialized"
          : "Caching event during loading process";
        console.log(`${logMessage}: ${event.event_id}`);
        cacheEvent(event);
        return;
      }

      // Compare the incoming event ID with the current event ID
      const comparison = compareEventIds(currentEventId.value, event.event_id);

      // Handle based on comparison result
      if (comparison < 0) {
        // Event already processed or duplicate
        console.log(`Ignoring already processed event: ${event.event_id}`);
        return;
      } else if (comparison > 0) {
        // Event out of sync, need refresh
        console.warn(
          `Event sequence out of sync: current=${currentEventId.value}, received=${event.event_id}`
        );
        fetchAggregation();
        return;
      }

      // Event is next in sequence (comparison === 0)
      // Update the current event ID
      currentEventId.value = event.event_id;

      // Apply the delta based on event type
      try {
        switch (event.event_type) {
          case EventType.ACCOUNT:
            updateAccount(event.data as AccountSchema);
            break;
          case EventType.POSITION:
            updatePosition(
              event.data as Partial<PositionSchema> & {
                vt_position_id: string;
                trade_app_name: string;
                gateway_name: string;
                last_update_time: string;
              }
            );
            break;
          case EventType.ORDER:
            updateOrder(
              event.data as Partial<OrderSchema> & {
                vt_order_id: string;
                trade_app_name: string;
                gateway_name: string;
                last_update_time: string;
              }
            );
            break;
          case EventType.TRADE:
            updateTrade(event.data as TradeSchema);
            break;
          case EventType.RISK_REPORT:
            updateRiskReport(event.data as RiskReportSchema);
            break;
          case EventType.LOG:
            updateLog(event.data as LogSchema);
            break;
          default:
            console.warn(`Unknown event type: ${event.event_type}`);
            return;
        }

        lastUpdateTime.value = new Date().toISOString();
        // Schedule throttled publish for aggregation changes only
        schedulePublish();
      } catch (err) {
        console.error(`Error processing event ${event.event_id}:`, err);
      }
    }

    /**
     * Initialize with full aggregation data
     * This should be called when the application starts or when a refresh is needed
     */
    function initializeData(data: AccountAggregationSchema): void {
      if (!data || !data.aggregation || !data.event_id) {
        console.error("Invalid aggregation data received:", data);
        return;
      }

      // Update the state
      aggregation.value = data.aggregation;
      currentEventId.value = data.event_id;
      isInitialized.value = true;
      lastUpdateTime.value = new Date().toISOString();

      console.log(
        `Initialized account aggregation data with event ID: ${currentEventId.value}`
      );

      processCachedEvents();
      // After snapshot + cached events processed, publish once immediately
      forcePublish();
    }

    /**
     * Compare two event IDs
     * Returns:
     *  -1 if id1 <= id2 (already processed or duplicate)
     *   0 if id2 is exactly the next expected ID after id1 (in sequence)
     *   1 if id2 is out of sync with id1 (needs refresh)
     */
    function compareEventIds(id1: string, id2: string): number {
      // If we're not initialized yet, we can't compare event IDs
      if (!isInitialized.value) return 0;

      // Check for empty event IDs
      if (!id1 || !id2) {
        console.warn("Comparing with empty event ID", { id1, id2 });
        return 1; // Consider it out of sync
      }

      try {
        const prefix1 = id1.substring(0, 3);
        const prefix2 = id2.substring(0, 3);
        const num1 = parseInt(id1.substring(3), 10);
        const num2 = parseInt(id2.substring(3), 10);

        // Check for NaN which would indicate parsing failure
        if (isNaN(num1) || isNaN(num2)) {
          console.warn("Invalid event ID format", { id1, id2 });
          return 1; // Out of sync
        }

        // If prefixes are different, it means the backend has restarted
        if (prefix1 !== prefix2) {
          return 1; // Out of sync
        }

        // If the numeric part of id2 is less than or equal to id1
        if (num2 <= num1) {
          return -1; // Already processed
        }

        // If id2 is exactly one more than id1, it's the next in sequence
        if (num2 === num1 + 1) {
          return 0; // Next in sequence
        }

        // Otherwise, there's a gap in the sequence, we need a refresh
        return 1; // Out of sync
      } catch (err) {
        console.error("Error parsing event IDs", { id1, id2, error: err });
        return 1; // Out of sync
      }
    }

    /**
     * Add an event to the cache with size limit enforcement
     */
    function cacheEvent(event: ServerSentEvent): void {
      if (!event || !event.event_id || !event.event_type) {
        console.warn("Attempting to cache invalid event:", event);
        return;
      }

      // If cache is at max size, remove the oldest event (FIFO)
      if (cachedEvents.length >= MAX_CACHE_SIZE) {
        cachedEvents.shift();
      }
      cachedEvents.push(event);
    }

    /**
     * Process events that were cached during refresh or before initialization
     */
    function processCachedEvents(): void {
      if (cachedEvents.length === 0) {
        return;
      }

      console.log(`Processing ${cachedEvents.length} cached events`);

      // Copy the current cache to a local variable
      const eventsToProcess = cachedEvents;

      // Clear the cache immediately to avoid potential issues with new events coming in
      cachedEvents = [];

      // Process events in order
      let processedCount = 0;
      for (const event of eventsToProcess) {
        // Call handleEngineEvent with the event's type, data, and ID
        handleEvent(event);
        processedCount++;
      }

      console.log(
        `Successfully processed ${processedCount} of ${eventsToProcess.length} cached events`
      );
    }

    /**
     * Update account data based on a delta event
     */
    function updateAccount(accountData: AccountSchema): void {
      const { gateway_name } = accountData;

      // Validate required fields
      if (!gateway_name || !accountData.last_update_time) {
        console.warn("Invalid account data:", accountData);
        return;
      }

      // Ensure the gateway account exists
      if (!aggregation.value.has(gateway_name)) {
        fetchAggregation();
        return;
      } else {
        // Update existing account
        const accountInfo = aggregation.value.get(gateway_name)!;
        accountInfo.account = accountData;
        accountInfo.last_update_time = accountData.last_update_time;
      }
    }

    /**
     * Update position data based on a delta event
     */
    function updatePosition(
      positionData: Partial<PositionSchema> & {
        vt_position_id: string;
        trade_app_name: string;
        gateway_name: string;
        last_update_time: string;
      }
    ): void {
      const { gateway_name, vt_position_id } = positionData;

      // Validate required fields
      if (!gateway_name || !vt_position_id || !positionData.last_update_time) {
        console.warn("Invalid position data:", positionData);
        return;
      }

      // Apply the position delta (partial or full)
      if (!aggregation.value.has(gateway_name)) {
        console.warn(
          `Cannot update position, missing account info for ${gateway_name}`
        );
        fetchAggregation();
        return;
      }

      const accountInfo = aggregation.value.get(gateway_name)!;
      const existingPosition = accountInfo.positions.get(vt_position_id);

      if (existingPosition) {
        // Update the existing position with the delta data
        const updatedPosition = {
          ...existingPosition,
          ...positionData
        };

        // Check if the updated position should be removed (0 volume and frozen)
        if (updatedPosition.volume === 0 && updatedPosition.frozen === 0) {
          // Remove the position - much cleaner with Map
          accountInfo.positions.delete(vt_position_id);
          console.log(
            `Removed position with ID ${vt_position_id} (0 volume and frozen)`
          );
        } else {
          // Update the existing position with the delta data
          accountInfo.positions.set(vt_position_id, updatedPosition);
        }
      } else {
        // Add new position - we should have complete data for new positions
        // If not, this could be a data inconsistency
        accountInfo.positions.set(
          vt_position_id,
          positionData as PositionSchema
        );
      }

      accountInfo.last_update_time = positionData.last_update_time;
    }

    /**
     * Update order data based on a delta event
     */
    function updateOrder(
      orderData: Partial<OrderSchema> & {
        vt_order_id: string;
        trade_app_name: string;
        gateway_name: string;
      }
    ): void {
      const { gateway_name, vt_order_id } = orderData;

      // Validate required fields
      if (!gateway_name || !vt_order_id) {
        console.warn("Invalid order data:", orderData);
        return;
      }

      // Ensure the gateway account exists
      if (!aggregation.value.has(gateway_name)) {
        fetchAggregation();
        return;
      }

      // Update existing order
      const accountInfo = aggregation.value.get(gateway_name)!;
      const existingOrder = accountInfo.orders.get(vt_order_id);
      if (existingOrder) {
        // Update the existing order with the delta data
        const updatedOrder = {
          ...existingOrder,
          ...orderData
        };
        accountInfo.orders.set(vt_order_id, updatedOrder);
      } else {
        // Add new order - we should have complete data for new orders
        accountInfo.orders.set(vt_order_id, orderData as OrderSchema);
      }
    }

    /**
     * Update trade data based on a delta event
     */
    function updateTrade(tradeData: TradeSchema): void {
      const { gateway_name, vt_trade_id } = tradeData;

      // Validate required fields
      if (!gateway_name || !vt_trade_id) {
        console.warn("Invalid trade data:", tradeData);
        return;
      }

      // Ensure the gateway account exists
      if (!aggregation.value.has(gateway_name)) {
        fetchAggregation();
        return;
      }
      // Update trade data
      const accountInfo = aggregation.value.get(gateway_name)!;
      accountInfo.trades.set(vt_trade_id, tradeData);
    }

    /**
     * Update risk report data based on a delta event
     */
    function updateRiskReport(reportData: RiskReportSchema): void {
      const { gateway_name, name } = reportData;

      if (!gateway_name || !name) {
        console.warn("Invalid risk report data:", reportData);
        return;
      }

      if (!aggregation.value.has(gateway_name)) {
        console.warn(
          `Cannot update risk report, missing account info for ${gateway_name}`
        );
        fetchAggregation();
        return;
      }

      const accountInfo = aggregation.value.get(gateway_name)!;
      accountInfo.risk_reports.set(name, reportData);
    }

    /**
     * Update log data based on a delta event
     */
    function updateLog(logData: LogSchema): void {
      const { trade_app_name, gateway_name } = logData;
      if (!trade_app_name || !gateway_name) {
        console.warn("Invalid log data:", logData);
        return;
      }
      // marketdata is not a trade app, so we don't need to update the log
      if (trade_app_name === "marketdata") {
        return;
      }
      // Ensure the gateway account exists
      if (!aggregation.value.has(gateway_name)) {
        fetchAggregation();
        return;
      }

      // Update trade data
      const accountInfo = aggregation.value.get(gateway_name)!;
      accountInfo.logs.push(logData);
      // if logs length > 100, remove the oldest one
      if (accountInfo.logs.length > 100) {
        accountInfo.logs.shift();
      }

      return;
    }

    /**
     * Fetch the full account aggregation data
     */
    async function fetchAggregation() {
      try {
        isLoading.value = true;
        const response = await getAccountAggregation();

        if (!response || !response.success) {
          throw new Error(
            `Failed to fetch account aggregation: ${response.message || "Unknown error"}`
          );
        }

        console.log("Account aggregation data received");
        initializeData(convertToMapStructure(response.data));
        return response.data;
      } catch (err) {
        console.error("Error fetching account aggregation:", err);
        throw err;
      } finally {
        isLoading.value = false;
      }
    }

    /**
     * Refresh the full account aggregation data from all
     * trade apps, this is used when the aggregate data incorrect
     */
    async function refreshAggregation() {
      if (isLoading.value) {
        console.log("Loading already in progress, not requesting another");
        return;
      }

      try {
        isLoading.value = true;
        const response = await refreshAccountAggregation();

        if (!response.success || !response.data) {
          throw new Error(
            `Failed to refresh account aggregation: ${response.message || "Unknown error"}`
          );
        }

        console.log("Account aggregation refresh data received");
        initializeData(convertToMapStructure(response.data));
        return response.data;
      } catch (err) {
        console.error("Error refreshing account aggregation:", err);
        ElMessage.error(`刷新账户聚合数据失败: ${err}`);
        throw err;
      } finally {
        isLoading.value = false;
      }
    }

    /**
     * Clean up resources when the component is unmounted
     */
    function dispose() {
      console.warn("Disposing account aggregation store");
      // Unregister event handlers
      eventEngine.off(EventType.ACCOUNT, handleEvent);
      eventEngine.off(EventType.POSITION, handleEvent);
      eventEngine.off(EventType.ORDER, handleEvent);
      eventEngine.off(EventType.TRADE, handleEvent);
      eventEngine.off(EventType.LOG, handleEvent);
      eventEngine.off(EventType.RISK_REPORT, handleEvent);

      // Clear pending publish timer
      if (publishTimer !== null) {
        clearTimeout(publishTimer);
        publishTimer = null;
      }

      // Reset state to initial values
      isInitialized.value = false;
      cachedEvents = [];
    }

    return {
      // State
      isConnected,
      isLoading,
      aggregation,
      currentEventId,
      isInitialized,
      lastUpdateTime,

      // Actions
      init,
      fetchAggregation,
      refreshAggregation,
      forcePublish,
      dispose
    };
  }
);
